Skip to content

improvement(hubspot): OAuth-native polling trigger replacing webhook flow#4705

Merged
waleedlatif1 merged 10 commits into
stagingfrom
waleedlatif1/hubspot-oauth-trigger-refactor
May 22, 2026
Merged

improvement(hubspot): OAuth-native polling trigger replacing webhook flow#4705
waleedlatif1 merged 10 commits into
stagingfrom
waleedlatif1/hubspot-oauth-trigger-refactor

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

@waleedlatif1 waleedlatif1 commented May 21, 2026

Summary

  • Replace 27 manual-setup webhook triggers (clientId/secret/appId/devApiKey + curl commands) with a single OAuth-native hubspot_poller trigger
  • Polls HubSpot's CRM Search API every minute using each user's OAuth token; supports contacts, companies, deals, tickets, and custom objects
  • Strict monotonic (timestamp, hs_object_id) cursor with id-based tiebreaker — no record loss even at same-millisecond bulk imports
  • Uses lastmodifieddate for contacts (HubSpot null bug on hs_lastmodifieddate), hs_lastmodifieddate for others
  • Added hubspotWebhookPoll cron to helm values; removed 26 dead per-event trigger configs and the webhook provider handler

Configurability (second commit)

  • Property autocompleteproperties and targetPropertyName populated live from the connected HubSpot account
  • Pipeline / stage / owner / list dropdowns — fetched from HubSpot per object type
  • Advanced multi-filter — JSON array of AND-combined filters with full HubSpot operator set (EQ/NEQ/CONTAINS_TOKEN/GT/GTE/LT/LTE/BETWEEN/IN/NOT_IN/HAS_PROPERTY/NOT_HAS_PROPERTY); AI wand to generate from description
  • "Property Changed" event variant — fires only when a specific property's value actually changes (per-record snapshot diff with bounded GC at 1000 entries)
  • List Membership object type — separate code path polling /crm/v3/lists/{listId}/memberships/join-order, watermark by membershipTimestamp
  • New selector contracts + routes: /api/tools/hubspot/{properties,lists,pipelines,owners}

Type of Change

  • Improvement
  • Feature

Testing

  • Type-check clean; bunx vitest run lib/webhooks/ triggers/ passes (61/61)
  • Verified against HubSpot CRM Search API docs (endpoint, filterGroups shape, sort spec, page limits, rate limits)
  • Production data check: 0 successful executions across all 9 deployed HubSpot triggers historically — hard break has zero real-world blast radius

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 21, 2026 11:59pm

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 21, 2026

PR Summary

High Risk
High risk because it removes the existing HubSpot webhook provider/trigger surface and introduces a new polling pipeline with cursoring/idempotency that directly affects workflow execution reliability and HubSpot API usage.

Overview
HubSpot triggers are consolidated and re-implemented as polling. The docs and HubSpot block config are updated to replace 27 manual webhook triggers with a single hubspot_poller trigger, changing trigger configuration from app keys/curl setup to an OAuth credential plus object/event selection and filters.

Adds new HubSpot polling implementation and selector endpoints. Introduces a hubspot polling provider (lib/webhooks/polling/hubspot.ts) with monotonic (timestamp, objectId) cursoring, optional property-change detection, and list-membership polling, registers HubSpot as a polling provider, and adds new OAuth-backed selector APIs/contracts (/api/tools/hubspot/{properties,lists,pipelines,owners}) used for trigger UI dropdown/autocomplete. The legacy HubSpot webhook provider handler and related trigger mocks/configs are removed.

Reviewed by Cursor Bugbot for commit 324741d. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 21, 2026

Greptile Summary

This PR replaces 27 individual HubSpot webhook trigger configurations with a single OAuth-native polling trigger (hubspot_poller) that fetches changes via HubSpot's CRM Search API every minute using each user's OAuth token.

  • Core polling engine (lib/webhooks/polling/hubspot.ts): strict monotonic (timestamp, hs_object_id) cursor with a cursorFrozen gate on the first failure; supports contacts/companies/deals/tickets/custom objects plus a separate list-membership code path; per-record property_changed snapshot diff with LRU-bounded GC.
  • New UI trigger (triggers/hubspot/poller.ts): live dropdowns for properties, lists, pipelines, stages, and owners; getScopesForService('hubspot') injects required CRM scopes into the OAuth grant.
  • Four new selector API routes (/api/tools/hubspot/{properties,lists,pipelines,owners}): standard authorizeCredentialUse + refreshAccessTokenIfNeeded pattern, registered in the contracts index.

Confidence Score: 5/5

Safe to merge — the polling core is well-designed, the cursor and failure-recovery logic are correct, and all four new API routes follow the established auth pattern.

The (timestamp, id) cursor with cursorFrozen failure gate, list-membership seed/resume path, and property-changed snapshot diff are all logically sound. The new selector API routes use the standard auth pattern correctly. The only finding is a minor placement of the count pagination parameter in a POST endpoint URL query string rather than its request body.

apps/sim/app/api/tools/hubspot/lists/route.ts — the count pagination parameter may need to move from the URL query string into the POST request body.

Important Files Changed

Filename Overview
apps/sim/lib/webhooks/polling/hubspot.ts Core polling engine: cursor-frozen failure-gate, two OR filter groups for strict (timestamp, id) monotonic cursor, list-membership seed/resume path, property-changed snapshot diff — all logic is sound.
apps/sim/triggers/hubspot/poller.ts Trigger UI definition with live fetchOptions; getScopesForService ensures required CRM scopes; condition/required wiring looks correct.
apps/sim/app/api/tools/hubspot/lists/route.ts Correct auth pattern; sets count=500 as URL query param on a POST search endpoint — pagination size likely needs to be in the request body.
apps/sim/app/api/tools/hubspot/properties/route.ts Correct auth, encodeURIComponent on path segment, filters hidden/archived properties. No issues.
apps/sim/app/api/tools/hubspot/pipelines/route.ts Standard pattern with encodeURIComponent; filters archived pipelines; returns nested stages. No issues.
apps/sim/app/api/tools/hubspot/owners/route.ts Paginated owner fetch (up to 10 pages x 100), filters archived owners. No issues.
apps/sim/lib/api/contracts/selectors/hubspot.ts Four typed selector contracts; properly exported and registered in selectorContractsByPath. No issues.
helm/sim/values.yaml Adds hubspotWebhookPoll cron at */1 * * * * with Forbid concurrency — consistent with other polling cron patterns.

Sequence Diagram

sequenceDiagram
    participant Cron as Cron (every minute)
    participant Poll as hubspotPollingHandler
    participant HS as HubSpot CRM API
    participant DB as providerConfig (DB)
    participant WF as processPolledWebhookEvent

    Cron->>Poll: pollWebhook(ctx)
    Poll->>DB: read config (watermarkMs, lastSeenObjectId, snapshot)

    alt "objectType == list_membership"
        Poll->>HS: "GET /lists/{id}/memberships/join-order?after=cursor"
        HS-->>Poll: results + paging.next.after
        loop seed phase
            Poll->>DB: update cursor, no emit
        end
        loop normal phase
            Poll->>WF: processPolledWebhookEvent(payload)
            WF-->>Poll: success/failure
        end
        Poll->>DB: "advance cursor only if failedCount == 0"
    else search-based
        alt first poll
            Poll->>DB: "seed watermarkMs = now"
        else subsequent poll
            Poll->>HS: "POST /objects/{type}/search (GroupA OR GroupB)"
            HS-->>Poll: results[]
            Note over Poll: client-sort (ts ASC, id ASC), slice to maxRecords
            loop each record
                alt property_changed and value unchanged
                    Poll->>Poll: skip, update snapshot LRU
                else
                    Poll->>WF: executeWithIdempotency
                    WF-->>Poll: success/throw
                    alt failure
                        Poll->>Poll: "cursorFrozen = true"
                    end
                end
                Poll->>Poll: advance cursor only if success and not frozen
            end
            Poll->>DB: persist watermark + snapshot
            alt all failed
                Poll->>DB: markWebhookFailed
            else
                Poll->>DB: markWebhookSuccess
            end
        end
    end
Loading

Reviews (4): Last reviewed commit: "fix(hubspot): Map-backed property snapsh..." | Re-trigger Greptile

Comment thread apps/sim/lib/webhooks/polling/hubspot.ts Outdated
Comment thread apps/sim/lib/webhooks/polling/hubspot.ts
Comment thread apps/sim/triggers/hubspot/poller.ts
Comment thread apps/sim/lib/webhooks/polling/hubspot.ts
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/webhooks/polling/hubspot.ts
Comment thread apps/sim/lib/webhooks/polling/hubspot.ts
…ot-oauth-trigger-refactor

# Conflicts:
#	scripts/check-api-validation-contracts.ts
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Comment thread apps/sim/lib/webhooks/polling/hubspot.ts
Comment thread apps/sim/lib/webhooks/polling/hubspot.ts
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 324741d. Configure here.

@waleedlatif1 waleedlatif1 merged commit 21c956c into staging May 22, 2026
14 checks passed
@waleedlatif1 waleedlatif1 deleted the waleedlatif1/hubspot-oauth-trigger-refactor branch May 22, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant